package models;

import java.util.ArrayList;

public abstract class Character extends Placeable{

	private int BASE_STR;
	private int BASE_AGI;
	private int BASE_VIT;
	private int BASE_INT;
	private int BASE_DEX;
	
	private int STR;
	private int AGI;
	private int VIT;
	private int INT;
	private int DEX;
	
	private boolean isDead;
	
	private String name;
	
	private Helm helm;
	private Armor armor;
	private Weapon weapon;
	private Offhand offhand;
	
	private int HP_max;
	private int MP_max;
	private int HP;
	private int MP;
	
	private ArrayList<Buff> buffs;
	
	private int DMG_min;
	private int DMG_max;
	private double attackspeed;
	private double hitrating;
	private int spellpower;
	private double castspeed;
	private int defence;
	private double blockchance;
	private double resistchance;
	
	private int STRPlus;
	private int AGIPlus;
	private int VITPlus;
	private int INTPlus;
	private int DEXPlus;
	private int HP_maxPlus;
	private int MP_maxPlus;
	private int DMG_minPlus;
	private int DMG_maxPlus;
	private double attackspeedPlus;
	private double hitratingPlus;
	private int spellpowerPlus;
	private double castspeedPlus;
	private int defencePlus;
	private double blockchancePlus;
	private double resistchancePlus;
	
	protected Character(int str, int agi, int vit, int i, int dex, String name) {
		this.BASE_STR = str;
		this.BASE_AGI = agi;
		this.BASE_VIT = vit;
		this.BASE_INT = i;
		this.BASE_DEX = dex;
		this.name = name;
		this.isDead = false;
		this.helm = null;
		this.armor = null;
		this.weapon = null;
		this.offhand = null;
		buffs = new ArrayList<Buff>();
		calculateStats();
		HP = HP_max;
		MP = MP_max;
	}

	public synchronized void kill() {
		isDead = true;
		HP = 0;
		MP = 0;
		buffs.clear();
		calculateStats();
	}
	
	public synchronized void revive() {
		isDead = false;
		calculateStats();
		HP = 1;
		MP = 0;
	}
		
	public synchronized void fullRevive() {
		revive();
		HP = HP_max;
		MP = MP_max;
	}
	
	public synchronized long tick() {
//		1. do action
// 		do NOT tick the buffs
		calculateStats();
		update();
		System.out.println(name + " ticked!");
		System.out.println(this.toString());
		return (long) (attackspeed * 1000); //this should really be castspeed, attackspeed or potiondrinkingtime etc
	}
	
	protected abstract void update();

	private void calculateStats() {
		/*
		 * 0. Zero the pluses
		 * 1. Calculate total item amounts
		 * 2. Calculate total buff amounts
		 * 3. Calculate primaries from stats + total buff amounts
		 * 4. Calculate max hp/mp from primaries + total buff amounts
		 * 5. Calculate secondaries from primaries + total buff amounts + current hp/mp
		 * 6. Check current hp/mp so that it does not exceed maximum
		 */
		
		zeroPluses();
		calculateTotalItemAmounts();
		calculateTotalBuffAmounts();
		calculatePrimaries();
		calculateMaxHpMp();
		calculateSecondaries();
		checkCurrentHpMp();
		
	}
	
	private void calculateTotalItemAmounts() {
		//add up the bonuses in the plus-variables from items
		if(helm != null) {
			for(Effect e : helm.getEffects()) {
				addEffectToPluses(e);
			}
		}
		if(armor != null) {
			for(Effect e : armor.getEffects()) {
				addEffectToPluses(e);
			}
		}
		if(weapon != null) {
			for(Effect e : weapon.getEffects()) {
				addEffectToPluses(e);
			}
		}
		if(offhand != null) {
			for(Effect e : offhand.getEffects()) {
				addEffectToPluses(e);
			}
		}
	}
	
	private void calculateTotalBuffAmounts() {
		//add up the bonuses in the plus-variables from buffs
		for(Buff b : buffs) {
			for(Effect e : b.getEffects()) {
				addEffectToPluses(e);
			}
		}
	}
	
	private void addEffectToPluses(Effect e) {
		switch(e.getStat()) {
		case STR:
			STRPlus = (int) (STRPlus + e.getAmmount());
			break;
		case AGI:
			AGIPlus = (int) (AGIPlus + e.getAmmount());
			break;
		case VIT:
			VITPlus = (int) (VITPlus + e.getAmmount());
			break;
		case INT:
			INTPlus = (int) (INTPlus + e.getAmmount());
			break;
		case DEX:
			DEXPlus = (int) (DEXPlus + e.getAmmount());
			break;
		case HP:
			HP_maxPlus = (int) (HP_maxPlus + e.getAmmount());
			break;
		case MP:
			MP_maxPlus = (int) (MP_maxPlus + e.getAmmount());
			break;
		case DMG_min:
			DMG_minPlus = (int) (DMG_minPlus + e.getAmmount());
			break;
		case DMG_max:
			DMG_maxPlus = (int) (DMG_maxPlus + e.getAmmount());
			break;
		case attackspeed:
			attackspeedPlus = attackspeedPlus + e.getAmmount();
			break;
		case hitrating:
			hitratingPlus = hitratingPlus + e.getAmmount();
			break;
		case spellpower:
			spellpowerPlus = (int) (spellpowerPlus + e.getAmmount());
			break;
		case castspeed:
			castspeedPlus = castspeedPlus + e.getAmmount();
			break;
		case defence:
			defencePlus = (int) (defencePlus + e.getAmmount());
			break;
		case blockchance:
			blockchancePlus = blockchancePlus + e.getAmmount();
			break;
		case resistchance:
			resistchancePlus = resistchancePlus + e.getAmmount();
			break;
		}
	}
	
	private void zeroPluses() {
		STRPlus = 0;
		AGIPlus = 0;
		VITPlus = 0;
		INTPlus = 0;
		DEXPlus = 0;
		HP_maxPlus = 0;
		MP_maxPlus = 0;
		DMG_minPlus = 0;
		DMG_maxPlus = 0;
		attackspeedPlus = 0;
		hitratingPlus = 0;
		spellpowerPlus = 0;
		castspeedPlus = 0;
		defencePlus = 0;
		blockchancePlus = 0;
		resistchancePlus = 0;
	}
	
	private void calculatePrimaries() {
		resetPrimaries();
		STR = Math.max(1, STR + STRPlus);
		AGI = Math.max(1, AGI + AGIPlus);
		VIT = Math.max(1, VIT + VITPlus);
		INT = Math.max(1, INT + INTPlus);
		DEX = Math.max(1, DEX + DEXPlus);
	}
	
	private void resetPrimaries() {
		STR = BASE_STR;
		AGI = BASE_AGI;
		VIT = BASE_VIT;
		INT = BASE_INT;
		DEX = BASE_DEX;
	}
	
	private void calculateMaxHpMp() {
		HP_max = (int) (VIT + (VIT * VIT * 0.25) + STR);
		HP_max = Math.max(1, HP_max + HP_maxPlus);
		MP_max = (int) (INT + (INT * INT * 0.25));
		MP_max = Math.max(1, MP_max + MP_maxPlus);
	}
	
	private void calculateSecondaries() {
		//damage
		if(weapon != null) {
			DMG_min = weapon.getDMG_min() + STR + DMG_minPlus;
			DMG_max = weapon.getDMG_max() + STR + DMG_maxPlus;
		} else {
			DMG_min = 1 + DMG_minPlus;
			DMG_max = STR + DMG_maxPlus;
		}
		DMG_min = Math.max(0, DMG_min);
		DMG_max = Math.max(DMG_max, DMG_min);
		
		//attackspeed
		if(weapon != null) {
			if(attackspeedPlus > 0) {
				attackspeed = weapon.getAttackspeed() * (Math.exp(- 0.007 * AGI)) * (1/(1+attackspeedPlus));
			} else {
				attackspeed = weapon.getAttackspeed() * (Math.exp(- 0.007 * AGI)) * (-attackspeedPlus+1);
			}
		} else {
			if(attackspeedPlus > 0) {
				attackspeed = (Math.exp(- 0.007 * AGI)) * (1/(1+attackspeedPlus));
			} else {
				attackspeed = (Math.exp(- 0.007 * AGI)) * (-attackspeedPlus+1);
			}
		}
		
		//hitrating
		if(weapon != null) {
			hitrating = weapon.getHitrating() + (AGI * 0.0025) + hitratingPlus;
		} else {
			hitrating = 0.25 + (AGI * 0.0025) + hitratingPlus;
		}
		hitrating = Math.max(0, hitrating);
		hitrating = Math.min(1, hitrating);
		
		//spellpower
		spellpower = (int) Math.max(0, INT + spellpowerPlus);
		
		//castspeed
		if(castspeedPlus > 0) {
			castspeed = (Math.exp(- 0.007 * DEX)) * (1/(1+castspeedPlus));
		} else {
			castspeed = (Math.exp(- 0.007 * DEX)) * (-castspeedPlus+1);
		}
		
		//defence
		defence = 0;
		if(helm != null)
			defence = defence + helm.getDefence();
		if(armor != null)
			defence = defence + armor.getDefence();
		if(offhand != null)
			defence = defence + offhand.getDefence();
		defence = (int) Math.max(0, defence + (AGI * 0.05) + defencePlus);
		
		//blockchance
		if(offhand != null)
			blockchance = Math.min(0.95, Math.max(0, offhand.getBlockchance() + blockchancePlus));
		else
			blockchance = 0;
		
		//resistchance
		resistchance = Math.max(0, Math.min(0.95, resistchancePlus));
		
	}
	
	private void checkCurrentHpMp() {
		
		if(HP > HP_max)
			HP = HP_max;
		if(MP > MP_max)
			MP = MP_max;
		
	}
	
	public String toString() {
		String temp = "";
		temp = temp + "Name: " + name + "\n";
		temp = temp + "Is dead: " + isDead + "\n";
		
		temp = temp + "STR: " + STR + "\n";
		temp = temp + "AGI: " + AGI + "\n";
		temp = temp + "VIT: " + VIT + "\n";
		temp = temp + "INT: " + INT + "\n";
		temp = temp + "DEX: " + DEX + "\n";
		
		temp = temp + "HP max: " + HP_max + "\n";
		temp = temp + "MP max: " + MP_max + "\n";
		temp = temp + "HP: " + HP + "\n";
		temp = temp + "MP: " + MP + "\n";
		
		temp = temp + "Min dmg: " + DMG_min + "\n";
		temp = temp + "Max dmg: " + DMG_max + "\n";
		temp = temp + "Attackspeed: " + attackspeed + "\n";
		temp = temp + "Hitrating: " + hitrating + "\n";
		temp = temp + "Spellpower: " + spellpower + "\n";
		temp = temp + "Castspeed: " + castspeed + "\n";
		temp = temp + "Defence: " + defence + "\n";
		temp = temp + "Blockchance: " + blockchance + "\n";
		temp = temp + "Resistchance: " + resistchance;
		
		for(Buff b : buffs) {
			temp = temp + "\nBuff : " + b.toString();
		}
		if(helm != null)
			temp = temp + "\nHelm : " + helm.toString();
		if(armor != null)
			temp = temp + "\nArmor : " + armor.toString();
		if(weapon != null)
			temp = temp + "\nWeapon : " + weapon.toString();
		if(offhand != null)
			temp = temp + "\nOffhand : " + offhand.toString();
		return temp;
	}
	
	public synchronized void applyBuff(Buff buff) {
		buffs.add(buff);
		calculateStats();
	}

	public Helm getHelm() {
		return helm;
	}

	public synchronized void setHelm(Helm helm) {
		this.helm = helm;
		calculateStats();
	}

	public Armor getArmor() {
		return armor;
	}

	public synchronized void setArmor(Armor armor) {
		this.armor = armor;
		calculateStats();
	}

	public Weapon getWeapon() {
		return weapon;
	}

	public synchronized void setWeapon(Weapon weapon) {
		this.weapon = weapon;
		calculateStats();
	}

	public Offhand getOffhand() {
		return offhand;
	}

	public synchronized void setOffhand(Offhand offhand) {
		this.offhand = offhand;
		calculateStats();
	}
	
	public String getName() {
		return name;
	}

	public synchronized void removeBuff(Buff b) {
		buffs.remove(b);
	}

	public int getSTR() {
		return STR;
	}

	public int getAGI() {
		return AGI;
	}

	public int getVIT() {
		return VIT;
	}

	public int getINT() {
		return INT;
	}

	public int getDEX() {
		return DEX;
	}
	
}
